AUTOMATH-68
Присвячується українській школі лямбди
Хоча кожна школя лямбда-числення вважає шо PTS тайпчекери — це непотрібна надлишкова вправа,
існує певна педагогічна практика, яку потрібно виконати. Наприклад Чьорч-кодування,
та імплементація тайпчекера для перевірки цих термів. Адже евалуатори лямбда числення,
як і вся математика, це не щось шо можна зрозуміти, а лиш те що можна запамʼятати
і навчитися використовувати.
Існує декілька канонічних імплементації залежних типів:
1) Авторський тайпчекер на Haskell від Террі Кокана разом з його статтею про Calculus of Construction 1988 року;
2) Тайпчекер "Simple, Easy" від Льоха, МакБрайда і Свієрсти від 2001;
3) Тайпчекер Ленарта Аугюстссона "Simpler, Easier!" від 2007 року;
4) Тайпчекер Групоїд Інфініті Henk від 2016 року.
Що стосується синтаксисів лямбда-числення то їх палітра описана в
окремій статті про школи лямбди [1],
і представлена в цій статті імплементація синтаксису відповідає намірам Террі Кокана
побудувати тайпчекер навколо AUT-68 синтаксису [2].
Імплементація тайпчекера базується на [4] і [3].
Синтаксичне дерево
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Henk {
Universe(i64),
Variable(String),
App(Box<Henk>, Box<Henk>),
Lambda(String, Box<Henk>, Box<Henk>),
Forall(String, Box<Henk>, Box<Henk>),
}
BNF граматика
AUT = U
| V
| [ V : AUT ] AUT
| ( V : AUT ) AUT
| AUT AUT
| ( AUT )
use crate::ast::Henk;
grammar;
// Copyright (c) Groupoid Infinity 2024
// AUT = U | V | [ V : AUT ] AUT | ( V : AUT ) AUT | AUT AUT | ( AUT )
Name: String = { r"[a-zA-Z_][a-zA-Z_\d]*" => <>.to_string() };
Universe: Henk = { <n:r"\*[\d]+"> => Henk::Universe(i64::from_str_radix(&n[1..],10).unwrap()), <n:r"\*"> => Henk::Universe(0) };
Variable: Henk = { <n:Name> => Henk::Variable(n) };
Lambda: Henk = { <l:"("> <v:Name> <s:":"> <t:Expr> <r:")"> <e:Expr1> => Henk::Lambda(v,Box::new(t),Box::new(e)) };
Forall: Henk = { <l:"["> <v:Name> <s:":"> <t:Expr> <r:"]"> <e:Expr1> => Henk::Forall(v,Box::new(t),Box::new(e)) };
Expr1: Henk = { Lambda, Forall, Expr2 };
Expr2: Henk = { <l:Expr2> <r:Expr3> => Henk::App(Box::new(l),Box::new(r)), <x:Expr3> => x };
Expr3: Henk = { Variable, Universe, <l:"("> <e:Expr1> <r:")"> => e };
pub Expr: Henk = { Expr1 };
Альфа рівність
pub fn alpha_eq(&self, another: &Henk) -> bool {
match (self, another) {
(&Henk::Universe(v1), &Henk::Universe(v2)) => v1 == v2,
(&Henk::Variable(ref v1), &Henk::Variable(ref v2)) => v1 == v2,
(&Henk::App(ref left1, ref right1),&Henk::App(ref left2, ref right2),) => left1.alpha_eq(&left2) && right1.alpha_eq(&right2),
(&Henk::Lambda(ref b1, ref l1, ref r1), &Henk::Lambda(ref b2, ref l2, ref r2),) => { l1.alpha_eq(l2) && r1.alpha_eq(&r2.clone().subst(&b2, &Henk::Variable(b1.clone())),) }
(&Henk::Forall(ref b1, ref l1, ref r1), &Henk::Forall(ref b2, ref l2, ref r2),) => { l1.alpha_eq(l2) && r1.alpha_eq(&r2.clone().subst(&b2, &Henk::Variable(b1.clone())),) }
_ => false,
}
}
Нормальні форми
pub fn nf(self) -> Henk {
fn spine(leftmost: Henk, stack: &[Henk]) -> Henk {
match (leftmost, stack) {
(Henk::App(left, right), _) => { let mut new_stack: Vec<Henk> = stack.into(); new_stack.push(*right); spine(*left, &new_stack) }
(Henk::Lambda(ref from, ref l, ref r), ref stack) if stack.is_empty() => { Henk::Lambda(from.clone(), Box::new(l.clone().nf()), Box::new(r.clone().nf()), ) }
(Henk::Lambda(ref from, _, ref r), ref stack) => { let mut ns: Vec<Henk> = (*stack).into(); let right = ns.pop().unwrap(); spine(r.clone().subst(&from, &right), &ns) }
(Henk::Forall(ref b, ref l, ref r), ref stack) => stack.iter().fold(Henk::Forall(b.clone(), Box::new(l.clone().nf()), Box::new(r.clone().nf()), ), |x, y| Henk::App(Box::new(x), Box::new(y.clone().nf()))),
(leftmost, _) => stack.iter().fold(leftmost, |l, r| Henk::App(Box::new(l), Box::new(r.clone().nf())) ),
}
}
spine(self, &[])
}
Підстановка
pub fn subst(self, from: &String, to: &Henk) -> Henk {
fn lambda(bound: String, left: Box<Henk>, right: Box<Henk>) -> Henk { Henk::Lambda(bound, left, right) }
fn forall(bound: String, left: Box<Henk>, right: Box<Henk>) -> Henk { Henk::Forall(bound, left, right) }
fn substitute_closure<'a>(from: &String, to: &Henk, bound: &'a String, left: &'a Box<Henk>, right: &'a Box<Henk>,
fun: fn (bound: String, left: Box<Henk>, right: Box<Henk>) -> Henk) -> Henk {
if bound == from { fun(bound.clone(),Box::new(left.clone().subst(from, to)),right.clone(),) }
else {
if !to.free_vars().contains(bound) { fun(bound.clone(), Box::new(left.clone().subst(from, to)), Box::new(right.clone().subst(from, to)),) }
else {
let mut unused: String = bound.clone(); unused.push_str("'");
loop {
let used: HashSet<&String> = right.free_vars().union(&to.free_vars()).cloned().collect();
if used.contains(&unused) { unused.push_str("'") }
else {
return fun(unused.clone(), Box::new(left.clone().subst(bound, &Henk::Variable(unused.clone()), )),
Box::new(right.clone().subst(bound, &Henk::Variable(unused)), ), ).subst(from, to);
}
}
}
}
}
match self {
Henk::Universe(v) => Henk::Universe(v),
Henk::Variable(v) => { if v == *from { to.clone() } else { Henk::Variable(v) } }
Henk::App(left, right) => { Henk::App(Box::new(left.subst(from, to)), Box::new(right.subst(from, to))) }
Henk::Lambda(ref bound, ref left, ref right) => { substitute_closure(from, to, bound, left, right, lambda) }
Henk::Forall(ref bound, ref left, ref right) => { substitute_closure(from, to, bound, left, right, forall) }
}
}
Вільні змінні
pub fn free_vars(&self) -> HashSet<&String> {
fn closure_vars<'a>(bound: &'a String, left: &'a Box<Henk>, right: &'a Box<Henk>) -> HashSet<&'a String> {
let mut tmp = right.free_vars();
tmp.remove(bound);
tmp.union(&left.free_vars()).cloned().collect()
}
let mut set = HashSet::new();
match *self {
Henk::Universe(v) => {}
Henk::Variable(ref v) => { set = HashSet::new(); set.insert(v); }
Henk::App(ref left, ref right) => { set = left.free_vars().union(&right.free_vars()).cloned().collect() }
Henk::Lambda(ref bound, ref left, ref right) => { closure_vars(bound, left, right); }
Henk::Forall(ref bound, ref left, ref right) => { closure_vars(bound, left, right); }
}
set
}
Тайпчекер
pub fn type_check_with_context(self, context: HashMap<String, Henk>) -> Result<Henk, String> {
match self {
Henk::Universe(n) => Ok(Henk::Universe(n + 1)),
Henk::Forall(bound, left, right) => { Ok(Henk::Universe(0)) }
Henk::Variable(v) => match context.get(&v) { Some(ty) => Ok(ty.clone()), None => Err(format!("Cannot find variable {}.", &v)), },
Henk::App(left, right) => {
match left.type_check_with_context(context.clone())? {
Henk::Forall(bound, ty_in, ty_out) => {
let right_ty = right.clone().type_check_with_context(context.clone())?;
if right_ty.clone().nf().alpha_eq(&ty_in.clone().nf()) { Ok(ty_out.subst(&bound, &right)) }
else { Err(format!("Expected something of type {}, found that of type {}.", ty_in, right_ty), ) }
}
left_ty => { Err(format!("Expected lambda, found value of type {}.", left_ty.nf())) } }
}
Henk::Lambda(bound, left, right) => {
let left_type = left.clone().type_check_with_context(context.clone())?;
let mut new_context = context;
new_context.insert(bound.clone(), *left.clone());
let right_type = right.type_check_with_context(new_context)?;
Ok(Henk::Forall(bound, left, Box::new(right_type)))
}
}
}
Приклади
(A:*) (H:A) (T:[L:*][C:[_:A][_:L]L][N:L]L) (L:*)
(C:[_:A][_:L]L) (N:L) (C H (T L C N))
(A: *) (Head: A) (Tail: [List: *] [Cons: [_:A] [_: List] List] [Nil: List] List)
(List: *) (Cons: [_:A] [_: List] List) (Nil: List)
(Cons Head (Tail List Cons Nil))
[Nat : *] [Succ : [_:Nat] Nat] [Zero : Nat] Nat
(pred : [Nat : *] [Succ : [_ : Nat] Nat] [Zero : Nat] Nat) (Nat : *)
(Succ : [_: Nat] Nat) (Zero : Nat) (Succ (pred Nat Succ Zero))
[Nat:*] [Succ: [_:Nat] Nat] [Zero: Nat] Nat
(x: [Nat:*] [Succ: [_:Nat] Nat] [Zero: Nat] Nat)
(x ([Nat:*][Succ:[_:Nat]Nat][Zero:Nat]Nat)
((pred: [Nat:*] [Succ: [_:Nat] Nat] [Zero:Nat] Nat)
(Nat2:*)
(Succ2:[_:Nat2] Nat2)
(Zero2: Nat2) (Succ2 (pred Nat2 Succ2 Zero2))))
Висновки
[1]. М. Сохацький. Школи лямбди.
[2]. Т. Кокан. Деякі ремарки щодо залежної теорії.
[3]. Л. Аугюстссон. Ще простіший тайпчекер.
[4]. М.Сохацький. Одноаксіоматична система